package com.lujianfei.ann.network;

import java.util.ArrayList;
import java.util.List;

import com.lujianfei.ann.network.activate.IActivateFunc;
import com.lujianfei.ann.network.layer.Layer;
import com.lujianfei.ann.network.utils.LogUtils;

public class AnnNetwork {

	List<Layer> layers = new ArrayList<Layer>();

	double rate = 1f;

	public double getRate() {
		return rate;
	}

	public void setRate(double rate) {
		this.rate = rate;
	}

	public void addLayer(Layer layer) {
		layers.add(layer);
	}

	public Layer getLayer(int index) {
		return layers.get(index);
	}
	
	public void init() {
		for (int layerIdx = 0; layerIdx < layers.size() - 1; layerIdx++) {
			Layer layer = layers.get(layerIdx);
			layer.getOutWeight().reset();
		}
	}

	public double train(double[] feedInput, double[] target) {

		// ===========================前向传播====================================
		// 通过输入层和权值计算各层节点值
		forward(feedInput);

		// ===========================反向传播===================================
		// 计算误差
		getError(target);
		// 更新权值
		updateWeight();

		double errors = 0;
		for (int i = 0; i < target.length; i++) {
			double e = target[i] - layers.get(layers.size() - 1).getN()[i + 1];
			errors += e * e;
		}

		return errors / (float)target.length;
	}

	private void forward(double[] feedInput) {

		layers.get(0).setN(feedInput);
		layers.get(0).setN(0, 1d);

		for (int layerIdx = 1; layerIdx < layers.size(); layerIdx++) {
			Layer preLayer = layers.get(layerIdx - 1);
			Layer currentLayer = layers.get(layerIdx);
			for (int rightIdx = 0; rightIdx < preLayer.getOutWeight().getW()[0].length; rightIdx++) {
				double total = 0;
				for (int leftIdx = 0; leftIdx < preLayer.getN().length; leftIdx++) {
					double x = preLayer.getN()[leftIdx];
					double w = preLayer.getOutWeight().getW()[leftIdx][rightIdx];
					total += x * w;
				}
				currentLayer.setN(rightIdx + 1, preLayer.getActivateFunc().f(total));
			}
		}
	}

	private void getError(double[] label) {

		Layer outputLayer = layers.get(layers.size() - 1);

		double[] errors = new double[outputLayer.getN().length - 1];

		for (int i = 0; i < errors.length; i++) {
			double y = outputLayer.getN()[i + 1];
			double dy = outputLayer.getActivateFunc().df(y);
			double z = label[i];
			errors[i] = (z - y) * dy;
		}
		outputLayer.setErrors(errors);

		for (int layerIdx = layers.size() - 2; layerIdx >= 0; layerIdx--) {
			Layer currentLayer = layers.get(layerIdx);
			Layer nextLayer = layers.get(layerIdx + 1);
			double[] errors2 = new double[currentLayer.getN().length - 1];

			for (int i = 0; i < errors2.length; i++) {
				double total = 0d;
				for (int j = 0; j < currentLayer.getOutWeight().getW()[i].length; j++) {
					double w = currentLayer.getOutWeight().getW()[i + 1][j];
					double e = nextLayer.getErrors()[j];
					total += w * e;
				}
				double dy = currentLayer.getActivateFunc().df(currentLayer.getN()[i + 1]);
				errors2[i] = total * dy;
			}
			currentLayer.setErrors(errors2);
		}
	}

	private void updateWeight() {

		for (int layerIdx = 0; layerIdx < layers.size() - 1; layerIdx++) {

			Layer currentLayer = layers.get(layerIdx);
			
			Layer nextLayer = layers.get(layerIdx + 1);

			for (int i = 0; i < currentLayer.getOutWeight().getW()[0].length; i++) {

				for (int j = 0; j < currentLayer.getN().length; j++) {

					double w = currentLayer.getOutWeight().getW()[j][i];

					double e = nextLayer.getErrors()[i];

					double x = currentLayer.getN()[j];

					w = w + rate * e * x;

					currentLayer.getOutWeight().setW(j, i, w);
				}
			}

		}
	}

	public void predict(double[] binary, double[] result) {
		// ===========================前向传播====================================
		// 通过输入层和权值计算各层节点值
		forward(binary);
		for (int i = 0; i < result.length; i++) {
			result[i] = layers.get(layers.size() - 1).getN()[i + 1];
		}
	}
	
	public void printWeight() {
		for (int layerIdx = 0; layerIdx < layers.size() - 1; layerIdx++) {
			Layer layer = layers.get(layerIdx);
			LogUtils.Println("w" + layerIdx, layer.getOutWeight().getW());
		}
	}
	public void printError() {
		for (int layerIdx = 0; layerIdx < layers.size(); layerIdx++) {
			Layer layer = layers.get(layerIdx);
			if(null == layer.getErrors()) continue;
			LogUtils.Println("error:" + layerIdx, layer.getErrors());
		}
	}
	public void printLayers() {
		for (int layerIdx = 0; layerIdx < layers.size(); layerIdx++) {
			Layer layer = layers.get(layerIdx);
			LogUtils.Println("layer" + layerIdx, layer.getN());
		}
	}
}
